#!/usr/bin/python2.7

import gtk
import gobject
from gtk import gdk
import GraphicalGraph
from Graph import *
from Vector2 import *
from Dependency import *
import MainWindow

CONTROL_KEYVAL = 65507

SELECT_MODE = 0
EDIT_MODE = 1

MOUSE_PRESSED = 0
MOUSE_RELEASED = 1

class GraphDisplayer(gtk.VBox):

    def __init__(self, engine):
        gtk.VBox.__init__(self)
        self.engine = engine

        self.drawingArea = gtk.DrawingArea()
        self.statusBar = gtk.Label("Status")
       
        self.nodes_model = gtk.ListStore(gobject.TYPE_PYOBJECT, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
        self.nodes_treeview = gtk.TreeView(self.nodes_model)
        self.nodes_treeview.connect("cursor-changed", self.treeviewCursorChanged)
        # create the TreeViewColumn to display the data
        node_name_column = gtk.TreeViewColumn('Dependency')
        # Allow sorting on the column
        node_name_column.set_sort_column_id(1)
        hidden_nodes_column = gtk.TreeViewColumn("Hidden")
        
        self.nodes_treeview.append_column(node_name_column)
        self.nodes_treeview.append_column(hidden_nodes_column)
        
        # create a CellRendererText to render the data
        textCell = gtk.CellRendererText()
        toggleCell = gtk.CellRendererToggle()
        # Can be activated by clicking on the toggle
        toggleCell.set_active(True)
        toggleCell.set_activatable(True)
        toggleCell.connect("toggled", self.toggleClicked)
        
        textCell.set_fixed_size(100, -1)
        # add the cell to the tvcolumn and allow it to expand
        node_name_column.pack_start(textCell, True)
        node_name_column.add_attribute(textCell, 'text', 1)
        
        hidden_nodes_column.pack_start(toggleCell, True)
        hidden_nodes_column.add_attribute(toggleCell, 'active', 2)
        
        swH = gtk.ScrolledWindow()
        swH.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
        swH.add(self.nodes_treeview)
       
        graphic_vbox = gtk.VBox()
        graphic_vbox.pack_start(self.drawingArea, True, True, 0)
        graphic_vbox.pack_start(self.statusBar, False, True, 0)

        main_hbox = gtk.HBox()
        main_hbox.pack_start(swH, False, True, 0)
        main_hbox.pack_start(graphic_vbox, True, True, 0)
        
        
        self.pack_start(main_hbox, True, True, 0)
        
        self.graph_absolute_position = Vector2(0,0)
        self.graph_drag = False
        self.node_drag = False
        self.graph = None
        self.last_mouse_position = Vector2(0, 0)
        self.dragged_nodes = [] 
        
        self.drawingArea.connect("expose-event", self.update_callback)

        self.drawingArea.connect("motion_notify_event", self.mouse_motion_event)
        self.drawingArea.connect("button_press_event", self.mouse_press_event)
        self.drawingArea.connect("button_release_event", self.mouse_release_event)
        self.connect("key_press_event", self.key_press_event)
        self.connect("key_release_event", self.key_release_event)

        self.drawingArea.set_events(gtk.gdk.EXPOSURE_MASK
                | gtk.gdk.LEAVE_NOTIFY_MASK
                | gtk.gdk.BUTTON_PRESS_MASK
                | gtk.gdk.BUTTON_RELEASE_MASK
                | gtk.gdk.KEY_PRESS_MASK
                | gtk.gdk.KEY_RELEASE_MASK
                | gtk.gdk.POINTER_MOTION_MASK)

        self.show_all()
        self.updateStatusBar()
        self.withLine = True
        self.key_pressed = []
        self.drag_nodes = False
        self.select_area = False
        self.create_edge = False
        self.parent_node = None
        
        # Set to true when the mouse has been pressed in the drawing area (do not moves the view when node clicked in the drawing area, cursor changed in the treeview when mouse pressed)
        self.drawingarea_clicked = True
        self.selected_nodes = []
        
        self.mode = SELECT_MODE
        self.mouse_state = MOUSE_RELEASED

    def setMode(self, mode):
        self.mode = mode
        self.selected_nodes = []
        self.updateStatusBar()
        self.update()

    # hidden toggle from the treeview
    def toggleClicked(self, cell, path):
        iter = self.nodes_model.get_iter(path)
        last = self.nodes_model.get_value(iter, 2)
        # Refresh graphics status in GraphicalGraph
        node = self.nodes_model.get_value(iter, 0)
        self.graph.graphics[node][2] = not last
        self.nodes_model.set(iter, 2, not last)
        self.update()
        
    def setGraph(self, graph):
        self.graph = graph
        self.updateGraphModel()
        self.updateStatusBar()

    def updateGraphModel(self):
        self.nodes_model.clear()
        for (node, (position, size, hidden)) in self.graph.graphics.items():
            self.nodes_model.append([node, node.name, hidden])
     
    def update(self):
        self.update_callback(None, None)

        
    
    # def update_cairo_callback(self, area, widget):
        # cairo = self.drawingArea.window.cairo_create()
        # self.drawingArea.window.clear()
        # if isinstance(self.graph,GraphicalGraph.GraphicalGraph):
            # g = self.graph
            # g.draw_cairo(cairo, self.graph_absolute_position)
    
    def update_callback(self, area, widget):
        style = self.drawingArea.get_style()
        gc = style.fg_gc[gtk.STATE_NORMAL]
        self.drawingArea.window.clear()
        
        select_gc = self.drawingArea.window.new_gc()
        select_gc.set_foreground(self.drawingArea.window.get_colormap().alloc_color("#FFFFFF"))
        
        if self.mouse_state == MOUSE_PRESSED and self.mouse_button == 1 and not self.drag_nodes:
            position = self.mouse_press_position.copy()
            size = self.mouse_motion_position - self.mouse_press_position.copy()
            

            width = size.getX() 
            x = position.getX()
            height = size.getY()
            y = position.getY()
            if width < 0:
                position.setX(x + width)
                size.setX(-width)
            if size.getY() < 0:
                position.setY(y + height)
                size.setY(-height)
            
            self.drawingArea.window.draw_rectangle(select_gc, True, int(position.getX()), int(position.getY()), int(size.getX()), int(size.getY()))
        
        
        if isinstance(self.graph,GraphicalGraph.GraphicalGraph):
            self.graph.draw(self.drawingArea, self.graph_absolute_position, self.selected_nodes, self.withLine)
        

            
    def mouse_press_event(self, widget, event):
        self.mouse_press_position = Vector2(int(event.x), int(event.y))
        self.last_mouse_position = self.mouse_press_position
        self.mouse_button = event.button
        self.mouse_state = MOUSE_PRESSED
        point = self.mouse_press_position - self.graph_absolute_position
        
        nodes = self.graph.nodesUnderPoint(point)
        #print len(nodes)
        
        if self.mouse_button == 1:
            if len(nodes) == 0:
                self.selectNodes(nodes)
            if len(nodes) == 1:
                if CONTROL_KEYVAL in self.key_pressed:
                    self.create_edge = True
                    self.parent_node = nodes[0]
                elif not nodes[0] in self.selected_nodes:
                    self.selectNodes(nodes)
                    self.drag_nodes = True
                elif nodes[0] in self.selected_nodes:
                    self.drag_nodes = True
        elif self.mouse_button == 3:
            if len(self.selected_nodes) == 0 and len(nodes) == 1:
                self.selectNodes(nodes)
                self.displayDependencyContextMenu(event, self.selected_nodes[0])
            if len(self.selected_nodes) == 1 and len(nodes)==1 and nodes[0] == self.selected_nodes[0]:
                self.displayDependencyContextMenu(event, self.selected_nodes[0])
            elif len(self.selected_nodes) > 1 and len(nodes)>0:
                if not nodes[0] in self.selected_nodes:
                    self.selectNodes(nodes)
                else:
                    self.displayMultipleDependenciesContextMenu(event, self.selected_nodes)
            else:
                self.displayGeneralContextMenu(event)

    def mouse_release_event(self, widget, event):
        self.mouse_release_position = Vector2(int(event.x), int(event.y))
        self.mouse_state = MOUSE_RELEASED
        point = self.mouse_release_position - self.graph_absolute_position
        nodes = self.graph.nodesUnderPoint(point)
        self.withLine = True
        
        if self.drag_nodes:
            self.drag_nodes = False
        
        if self.mouse_button == 1:
            if self.select_area:
                nodes = self.graph.nodesInBox(self.mouse_press_position - self.graph_absolute_position, self.mouse_release_position - self.graph_absolute_position)
                self.selectNodes(nodes)
                self.withLine = True
                self.select_area = False
                self.update()
            if self.create_edge and len(nodes) == 1:
                child = nodes[0]
                self.graph.addEdge(self.parent_node, child)
                self.update()
                self.create_edge = False  
        self.parent_node = None
               
        
    def mouse_motion_event(self, widget, event):
        self.mouse_motion_position = Vector2(int(event.x), int(event.y))
        
        nodes = self.graph.nodesUnderPoint(self.mouse_motion_position - self.graph_absolute_position)
        msg = ""
        if len(nodes) == 1:
            msg = nodes[0].name
        self.updateStatusBar(msg)
        
        
        state = event.state
        delta = self.mouse_motion_position - self.last_mouse_position
        self.last_mouse_position = self.mouse_motion_position
        
        if self.mouse_state == MOUSE_PRESSED:
            if self.mouse_button == 1 :
                if not self.drag_nodes and not self.create_edge:
                    self.select_area = True
                    self.update()
                elif self.create_edge:
                    pass
                else:	
                    for node in self.selected_nodes:
                        node_position = self.graph.getNodePosition(node)
                        self.graph.moveNodeTo(node, node_position + delta)
                        self.update()
            elif(self.mouse_button == 2):	
                
                self.graph_absolute_position = self.graph_absolute_position + delta 
                
                self.update()
            

        



    # Saves the keys that have been pressed
    def key_press_event(self, widget, event):
        if not event.keyval in self.key_pressed:
            self.key_pressed.append(event.keyval)
            print "press=", event.keyval
    
    # Removes the keys that have been pressed from the array
    def key_release_event(self, widget, event):
        if event.keyval in self.key_pressed:
            self.key_pressed.remove(event.keyval)
            print "release=", event.keyval
            
            
    def displayGeneralContextMenu(self, event):
        menu = gtk.Menu()
        create_node_menu_item = gtk.MenuItem("Create node")
        filter_menu_item = gtk.MenuItem("Filter")
        center_view_menu_item = gtk.MenuItem("Center view")
        filter_menu_item.connect("activate", self.openFilterBox)	
        create_node_menu_item.connect("activate", self.createNodeHandler)	
        center_view_menu_item.connect("activate", self.centerViewHandler)	
        menu.append(create_node_menu_item)
        menu.append(filter_menu_item)
        menu.append(center_view_menu_item)
        create_node_menu_item.show()	
        filter_menu_item.show()	
        center_view_menu_item.show()	
        menu.popup(None, None, None, event.button, event.time)
        # Define this location to create a node at this place
        self.newNodeLocation = Vector2(event.x, event.y) - self.graph_absolute_position
        
    def displayDependencyContextMenu(self, event, dependency):
        menu = gtk.Menu()
        description_menu_item = gtk.MenuItem("Description")
        description_menu_item.connect("activate", self.showDependencyWindow, dependency)	

        remove_menu_item = gtk.MenuItem("Remove")
        remove_menu_item.connect("activate", self.removeDependency, dependency)

        execute_menu_item = gtk.MenuItem("Execute")
        execute_menu_item.connect("activate", self.executeDependency, dependency)	
        
        extract_menu_item = gtk.MenuItem("Extract dependency graph")
        extract_menu_item.connect("activate", self.extractDependencyGraph, dependency)	
        
        menu.append(description_menu_item)
        menu.append(remove_menu_item)
        menu.append(execute_menu_item)
        menu.append(extract_menu_item)

        description_menu_item.show()		
        execute_menu_item.show()
        remove_menu_item.show()
        extract_menu_item.show()
        menu.popup(None, None, None, event.button, event.time)
        
        
    def displayMultipleDependenciesContextMenu(self, event, dependencies):
        menu = gtk.Menu()
        # If two nodes selected, can remove edge
        if len(dependencies) == 2:
            dep0 = dependencies[0]
            dep1 = dependencies[1]
            edge1 = [dep0, dep1]
            edge2 = [dep1, dep0]
            
            check_path1_menu_item = gtk.MenuItem("Check path " + dep0.name + "->" +dep1.name)
            check_path1_menu_item.connect("activate", self.checkPath, dep0, dep1)
            menu.append(check_path1_menu_item)
            check_path1_menu_item.show()
            
            check_path2_menu_item = gtk.MenuItem("Check path " + dep1.name + "->" +dep0.name)
            check_path2_menu_item.connect("activate", self.checkPath, dep1, dep0)
            menu.append(check_path2_menu_item)
            check_path2_menu_item.show()
            
            if self.graph.edgeExists(edge1):
                remove1_menu_item = gtk.MenuItem("Remove edge " + dep0.name + "->" + dep1.name)
                remove1_menu_item.connect("activate", self.removeEdge, edge1)
                menu.append(remove1_menu_item)
                remove1_menu_item.show()
            if self.graph.edgeExists(edge2):
                remove2_menu_item = gtk.MenuItem("Remove edge " + dep1.name + "->" + dep0.name)
                remove2_menu_item.connect("activate", self.removeEdge, edge2) 
                remove2_menu_item.show()
                menu.append(remove2_menu_item)
        
        remove_nodes_menu_item = gtk.MenuItem("Remove nodes")
        menu.append(remove_nodes_menu_item)
        remove_nodes_menu_item.show()
        menu.popup(None, None, None, event.button, event.time)
        

    def showDependencyWindow(self, widget, dependency):
        dialog = MainWindow.DependencyDialog(dependency, self.graph)
        dialog.add_button("Modify", 0)
        dialog.add_button("Close", 1)
        
        if dialog.run() == 0:
            dep = dialog.getDependency()
            dependency.fill(dep)
            self.updateGraphModel()
            self.updateStatusBar()
            self.update()
        dialog.destroy()

    def createNodeHandler(self, widget):
        newNode = DependencyNode("")
        # newNodeLocation has been saved when clicked
        position = self.newNodeLocation
        self.graph.addNode(newNode, position, hidden=True)
        
        dependencyDialog = MainWindow.DependencyDialog(newNode, self.graph)
        dependencyDialog.add_button("Save", 0)
        dependencyDialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
        retcode = dependencyDialog.run()
        if retcode == 0:
            dep = dependencyDialog.getDependency()
            if dep.name <> "":
                newNode.fill(dep)
                self.graph.setHidden(newNode, False)
            
                self.updateGraphModel()
                self.updateStatusBar()
                self.update()
            else:
                self.graph.removeNode(newNode)
        else:
            self.graph.removeNode(newNode)
        dependencyDialog.destroy()

    def centerViewHandler(self, widget):
        self.graph_absolute_position = Vector2(0, 0)
        self.update()
        
        
    def centerViewOnNode(self, node):
        position = self.graph.graphics[node][0]
        w = self.get_allocation().width / 2
        h = self.get_allocation().height / 2
        half_screen = Vector2(w, h)
        self.graph_absolute_position = Vector2(0,0) - position + half_screen
        self.update()
        

    def updateStatusBar(self, status = ""):
        if self.graph is None:
            self.statusBar.set_text("Graph not loaded yet")
        else:
            nb_nodes = len(self.graph.nodes)
            if status <> "":
                self.statusBar.set_text(status + " --- " + str(nb_nodes) + " nodes in the graph")
            else:
                self.statusBar.set_text(str(nb_nodes) + " nodes in the graph")

    def arrangeGraph(self):
        cols = 30
        space = 100
        current_col = 0
        current_row = 0
        nb = 0
        for node in self.graph.nodes.values():
            current_col = nb % cols
            current_row = int(nb / cols)
            v = Vector2(int(current_col*space), int(current_row*space))
            self.graph.moveNodeTo(node, v)
            nb = nb +1
        self.update()
        
    
    def selectNodes(self, nodes):
        self.selected_nodes = nodes
        self.update()
    
    
    # GUI
    def openFilterBox(self, widget):
        window = MainWindow.FilterWindow(self)
        window.show()
    
    
    
    def treeviewCursorChanged(self, treeview):
        """ A dependency has been clicked """
               
        current_path = treeview.get_cursor()[0]
        if not current_path is None:
            iter = treeview.get_model().get_iter(current_path)
            node = treeview.get_model().get_value(iter, 0)
            self.selectNodes([node])
            # center view on node
            
            # If we click in the drawing area, we do not want the cursor to move
            
            if not self.drawingarea_clicked:
                self.centerViewOnNode(node)
        self.drawingarea_clicked = False
    
    
    
    def hideNodes(self, nodes):
        for node in nodes:
            self.graph.graphics[node][2] = True
        self.updateGraphModel()
        self.update()
        
        
    def showNodes(self, nodes):
        for node in nodes:
            self.graph.graphics[node][2] = False
        self.updateGraphModel()
        self.update()
        
        
    def executeDependency(self, widget, dependency):
        self.emit("execute_asked", self.graph, dependency)
        
        
    def removeDependency(self, widget, dependency):
        self.graph.removeNode(dependency)
        self.updateGraphModel()
        self.update()
        
    def removeEdge(self, widget, edge):
        self.graph.removeEdge(edge)
        self.update()
        
    # checks whether a path exists
    def checkPath(self, widget, dep1, dep2):
        path = self.graph.find_path(dep1, dep2)
        if not path is None:
            print "Path =", [dep.name for dep in path]
        else:
            print "No path"
            
            
    def extractDependencyGraph(self, widget, dependency):
        g = self.graph.subgraph(dependency)
        f = open("graph_extraction.txt", "w")
        for node in g.nodes.values():
            f.write(node.name + ":" + ", ".join([dep.name for dep in g.children(node)]) + "\n")
        f.close()
            